import numpy as np


class GeneralTools:

    #
    def __init__(self):
        pass

    # this function
    def get_bin(self, start, end, bin_number: int) -> np.ndarray:
        if type(bin_number) is not int:
            try:
                non_integer_part = bin_number % 1
                if non_integer_part != 0:
                    raise ValueError('wrong number of bins')
            except TypeError:
                raise TypeError('type of the bin number is wrong')
        #     如果不是整数，尝试将其转换为整数。如果转换失败，抛出类型错误。
        interval = (end - start) / bin_number
        float_overflow_revise = 0.0000001
        bin1 = np.arange(start, end + interval - float_overflow_revise, interval)
        # 返回生成的分箱数组。
        return bin1

    # this function digitize a point sequence
    # parameter outlier_handling means how to deal with data in array1 that is not in any bins i.e. in left or right of
    # the bin1 array. then if outlier_handling is 'label', the indices label outlier by -1 in indices returned
    # if outlier_handling is 'error' , the program reports an error.
    def get_bin_index(self, point_array: np.ndarray, bin_array: np.ndarray,
                      outlier_handling: str = 'label') -> np.ndarray:
        if not isinstance(point_array, np.ndarray):
            raise TypeError('type of given array is wrong')
        if not isinstance(bin_array, np.ndarray):
            raise TypeError('type of given bin is wrong')
        indices = np.searchsorted(bin_array, point_array)
        # 使用 np.searchsorted 函数找到 array1 中每个元素在 bin1 中的插入位置，得到初步的bin索引 indices
        max_bin_index = bin_array.size - 1
        left_outlier = (indices <= 0)
        if left_outlier.any():
            left_outlier_indices = np.arange(indices.size)[left_outlier]
            # 获取 left_outlier 数组中所有为 True 的索引
            # np.arange(indices.size)生成一个从 0 到 indices.size - 1 的整数数组，表示 indices 数组的所有索引
            # 使用布尔数组 left_outlier 对生成的整数数组进行过滤，只保留 left_outlier 为 True 的索引
            for index in left_outlier_indices:
                continuous_value = point_array[index]
                if bin_array[0] - continuous_value < 0.0001:
                    indices[index] = 1
        right_outlier = (indices > max_bin_index)
        if right_outlier.any():
            right_outlier_indices = np.arange(indices.size)[right_outlier]
            for index in right_outlier_indices:
                continuous_value = point_array[index]
                if continuous_value - bin_array[-1] < 0.0001:
                    indices[index] = max_bin_index
        outlier_indicator = (indices <= 0) | (indices > max_bin_index)  # 找出异常点
        indices = indices - 1
        if outlier_handling == 'label':
            indices[outlier_indicator] = -1
        if outlier_handling == 'error':
            if outlier_indicator.any():
                raise ValueError('array to digitize has outlier, which is illegal')
        return indices

    # this function gives result of digitize of a 2-dimensional array by two bins. it is how a point is digitized.
    # be careful, the result is y and x, not x and y
    def get_points_bin_index(self, point_array: np.ndarray, x_bin_array: np.ndarray,
                             y_bin_array: np.ndarray) -> np.ndarray:
        x_result = self.get_bin_index(point_array[:, 0], x_bin_array)  # 提取二维数组 point_array 的第一列的所有元素
        y_result = self.get_bin_index(point_array[:, 1], y_bin_array)
        column_result = np.reshape(x_result, (-1, 1))  # 将 x_result 数组转换为一个二维数组
        row_result = np.reshape(y_result, (-1, 1))
        combine_result = np.concatenate((row_result, column_result), axis=1)
        return combine_result

    #
    def unrepeated_int_array(self, sequence: np.ndarray):
        unrepeated_index_array = []
        frequency_array = []
        unrepeated_index_array.append(sequence[0])
        repeat_counter = 0
        for point1 in sequence:
            if point1 == unrepeated_index_array[-1]:
                repeat_counter += 1
            else:
                unrepeated_index_array.append(point1)
                frequency_array.append(repeat_counter)
                repeat_counter = 1
        frequency_array.append(repeat_counter)
        unrepeated_index_array = np.asarray(unrepeated_index_array, dtype=int)
        frequency_array = np.asarray(frequency_array, dtype=int)
        return unrepeated_index_array, frequency_array

    # this function calculates density giving a single index array
    def density_of_single_array(self, whole_cell_number: int, cell_index_array: np.ndarray) -> np.ndarray:
        # 去重，unique_cell_row_col_array是去重后数组，frequency是每个元素出现的次数
        unique_cell_index_array, frequency = np.unique(cell_index_array, return_counts=True)
        wrong_cell_index = (unique_cell_index_array >= whole_cell_number)
        if wrong_cell_index.any():
            raise IndexError('array has wrong cell index')
        whole_frequency_array = np.zeros(whole_cell_number)
        for index in range(unique_cell_index_array.size):
            cell_index = unique_cell_index_array[index]
            cell_frequency = frequency[index]
            whole_frequency_array[cell_index] = cell_frequency
        return whole_frequency_array

    # this function transfers a list of positions into a list of indices
    def transfer_neighbor_row_col_pair_to_index(self, large_neighbor_dict, reference_dict: dict):
        # input_list是邻居字典，reference_dict是大网格位置字典
        if isinstance(large_neighbor_dict, list):
            neighbor_index_dict = []
            for input_element in large_neighbor_dict:
                if isinstance(input_element, np.ndarray):
                    input_element = tuple(input_element)
                index = reference_dict[input_element]
                index.append(index)
        if isinstance(large_neighbor_dict, dict):
            neighbor_index_dict = {}
            for key in large_neighbor_dict:
                row_col = large_neighbor_dict[key]
                if row_col is not False:
                    row_col = tuple(row_col)
                    output_index = reference_dict[row_col]
                    neighbor_index_dict[key] = output_index
        else:
            raise TypeError('wrong data to transfer')
        return neighbor_index_dict  # 将邻居字典中的位置（1，2）元素转换为索引字典index：1*2+2=4

    # this function
    def draw_by_probability(self, candidates, probability):
        if candidates.size == 0:
            raise ValueError('candidates can not be empty')
        if np.min(probability) < 0:
            raise ValueError('probability should not be nagative')

        pro_sum = np.sum(probability)
        if np.isinf(pro_sum):  # 无穷大处理
            print('this is inf')
            print(probability)
            print(np.arange(probability.size)[probability > 1000000000])
        if pro_sum == 0:  # 概率和为0处理
            probability = probability + 1 / probability.size
        probability = probability / np.sum(probability)  # 归一化
        if np.isnan(probability).any():  # 归一化后如果存在NaN，选择最后一个元素作为结果
            result = candidates[-1]
        else:
            try:
                result = np.random.choice(candidates, p=probability)  # 根据概率，随机选择一个元素，概率总和必须为1
            except ValueError:
                print('probabilities do not sum to 1, sum is {}'.format(np.sum(probability)))
                print(pro_sum)
                print(probability)
        return result  # 数值

    #
    def draw_by_probability_without_an_element(self, candidates, probability, element_index_to_delete):
        if candidates.size == 0:
            raise ValueError('candidates can not be empty')
        if np.min(probability) < 0:
            raise ValueError('probability should not be nagative')

        element_indicator = np.ones(candidates.size, dtype=bool)  # candidates.size = all_state_number
        if isinstance(element_index_to_delete, int):
            element_indicator[element_index_to_delete] = False
        else:
            for index_to_del in element_index_to_delete:  # [-2, -1]
                element_indicator[index_to_del] = False  # 去除两个虚拟节点
        good_candicates = candidates[element_indicator]  # 保留true的元素
        good_probability = probability[element_indicator]
        result = self.draw_by_probability(good_candicates, good_probability)  # 根据概率随机选择一个状态
        # 数值
        return result

    #
    def random_pick_element(self, element_array: np.ndarray):
        probability = np.zeros(element_array.shape) + 1 / element_array.size
        picked_element = np.random.choice(element_array, p=probability)
        return picked_element

    #
    def sample_from_interval(self, low, high):
        length = high - low
        random_number = np.random.random_sample()  # 生成一个在 [0, 1) 之间的随机数
        result = low + random_number * length
        return result

    #
    def neighbors_usable_indices_of_states(self, states, neighbor_dict):
        neighbor_pool = []
        for state in states:
            neighbor_pool = neighbor_pool + neighbor_dict[state].tolist()
        neighbor_pool = np.array(neighbor_pool, dtype=int)
        neighbor_pool = np.unique(neighbor_pool)
        return neighbor_pool
    #
    def level1_array_length(self, array, grid):
        subcell_array = array
        level1_array = grid.subcell_index_to_large_cell_index_dict[subcell_array]
        gt1 = GeneralTools()
        unrepeated_array = gt1.unrepeated_int_array(level1_array)[0]
        length = unrepeated_array.size
        return length

    #
    def intervals_overlap(self, interval1, interval2):
        if np.min(interval1) >= np.max(interval2):
            return False
        elif np.max(interval1) <= np.min(interval2):
            return False
        else:
            return True

